package pl.edu.agh.intobl;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.uncommons.maths.random.MersenneTwisterRNG;
import org.uncommons.maths.random.Probability;
import org.uncommons.watchmaker.framework.CandidateFactory;
import org.uncommons.watchmaker.framework.EvolutionEngine;
import org.uncommons.watchmaker.framework.EvolutionObserver;
import org.uncommons.watchmaker.framework.EvolutionaryOperator;
import org.uncommons.watchmaker.framework.FitnessEvaluator;
import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine;
import org.uncommons.watchmaker.framework.PopulationData;
import org.uncommons.watchmaker.framework.SelectionStrategy;
import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
import org.uncommons.watchmaker.framework.selection.TournamentSelection;
import org.uncommons.watchmaker.framework.termination.GenerationCount;
import org.uncommons.watchmaker.swing.evolutionmonitor.EvolutionMonitor;

import pl.edu.agh.intobl.evaluators.RouteSetEvaluator;
import pl.edu.agh.intobl.factories.RouteSetFactory;
import pl.edu.agh.intobl.operators.RouteSetCrossOver;
import pl.edu.agh.intobl.operators.RouteSetDisplacement;
import pl.edu.agh.intobl.operators.RouteSetInsertion;
import pl.edu.agh.intobl.operators.RouteSetInversion;
import pl.edu.agh.intobl.operators.RouteSetSwap;
import pl.edu.agh.intobl.utils.InputParser;

/**
 * Solver for a problem specified in InputParser
 */
public class ProblemSolver {
	
	public static Integer vehiclesNr;
	public static Integer vehiclesCapacity;
	public static Map<Integer, Integer> demands = new HashMap<Integer, Integer>();
	public static Integer[][] distanceMatrix;
	private static EvolutionMonitor monitor = new EvolutionMonitor();
	
	public static void start(InputParser parser) {
		monitor.showInFrame("population", true);
		vehiclesCapacity = parser.getVehiclesCapacity();
		vehiclesNr = parser.getVehiclesNumber();
		distanceMatrix = parser.getDistanceMatrix();
		for (int i=0; i<parser.getDemands().length; i++)
			demands.put(i+1, parser.getDemands()[i]);
		
		//TODO: in order to use evolution strategies use EvolutionEngine instead
		EvolutionEngine<RouteSet> engine = new GenerationalEvolutionEngine<RouteSet>(
				getCandidateFactory(), getEvolutionaryOperator(), getFitnessEvaluator(), 
				getSelectionStrategy(), getRandomNumberGenerator());

		//TODO: modify observer
		engine.addEvolutionObserver(new EvolutionObserver<RouteSet>() {
			public void populationUpdate(PopulationData<? extends RouteSet> data) {
				System.out.printf("Generation %d: %s %f\n", data.getGenerationNumber(), data.getBestCandidate(), data.getBestCandidateFitness());
				monitor.populationUpdate(data);
			}
		});

		RouteSet result = engine.evolve(10, 0, new GenerationCount(1000));
		System.out.println(result);
	}

	private static CandidateFactory<RouteSet> getCandidateFactory() {
		return new RouteSetFactory();
	}

	private static EvolutionaryOperator<RouteSet> getEvolutionaryOperator() {
		List<EvolutionaryOperator<RouteSet>> operators = new LinkedList<EvolutionaryOperator<RouteSet>>();
		operators.add(new RouteSetCrossOver(2, new Probability(0.75)));
		operators.add(new RouteSetSwap(0.05));
		operators.add(new RouteSetInversion(0.1));
		operators.add(new RouteSetInsertion(0.05));
		operators.add(new RouteSetDisplacement(0.15));

		return new EvolutionPipeline<RouteSet>(operators);
	}

	private static FitnessEvaluator<RouteSet> getFitnessEvaluator() {
		return new RouteSetEvaluator();
	}

	private static SelectionStrategy<Object> getSelectionStrategy() {
		return new TournamentSelection(new Probability(0.85));
	}

	private static Random getRandomNumberGenerator() {
		return new MersenneTwisterRNG();
	}
}

//TODO: pomysly na testy:
//		pamietac o sciezkach negatywnych i pozytywnych
//		sprawdzenie czy wszystkie demandery znalazly sie na liscie tras routeSeta
//		oblozenie wszystkich metod czy dzialaja
//		krzyzowanie czy zwraca poprawny wynik i inne poprawnosci
//		w szczegolnosci zwrocic uwage na inserty i tym podobne operacje na lancuchac... indeksy itd
//		koniecnzie trzeba przetestowac opeartory... zalozylem ze beda operowac na referencjach ale nie wiadomo ;)
